package oop.ex3.nameserver;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;

import oop.ex3.protocol.Begin;
import oop.ex3.protocol.ContainFile;
import oop.ex3.protocol.DontContainFile;
import oop.ex3.protocol.EndSession;
import oop.ex3.protocol.GoAway;
import oop.ex3.protocol.Goodbye;
import oop.ex3.protocol.MessageHandler;
import oop.ex3.protocol.ServerMessageSender;
import oop.ex3.protocol.ServerSideHandler;
import oop.ex3.protocol.WantAllFiles;
import oop.ex3.protocol.WantFile;
import oop.ex3.protocol.WantServers;
import oop.ex3.sharedclasses.IPaddress;

//TODO javadoc
public class SessionThread extends Thread {
	private NameServer _server;
	private Socket _clientSocket;
	private IPaddress _clientIP;
	private DataInputStream _clientInput;
	private DataOutputStream _clientOutput;

	private static final ServerSideHandler NO_SUCH_MESSAGE=null;
	private static final boolean FAILURE = false, SUCCESS=true;
	
	/**
	 * Constructs a new SessionThread.
	 * @param clientSocket the client's Socket.
	 */
	public SessionThread(Socket clientSocket, NameServer server){
		System.out.println("%%%%% NEW SESSION THREAD %%%%%");
		
		this._clientSocket=clientSocket;
		this._server=server;
		this._clientIP=new IPaddress();
		try {
			this._clientInput = new DataInputStream(this._clientSocket.getInputStream());
			this._clientOutput = new DataOutputStream(this._clientSocket.getOutputStream());
		
		} catch (IOException e) {
			closeStreams();
			// TODO handle exception what to do?!
		}
	}
	
	/**
	 * Closes this session's streams.
	 */
	private void closeStreams(){
		//TODO check for closing socket too or not
		try {
			this._clientInput.close();
			this._clientOutput.close();
			this._clientSocket.close();
		} catch (IOException e) {
			// TODO handle exception
		}
	}
	
	/**
	 * Generates a MessageHandler according to <message> input.
	 * Will return an instance of the correct MessageHandler
	 * that fits the input <message> String.
	 * The generator will not generate Begin, End Session or Go away
	 * since they are all treated differently than a standard message.
	 * @param message the input Message String.
	 * @return an instance of MessageHandler matching input <message>,
	 * 			or NO_SUCH_MESSAGE (=null object) if no matching Handler
	 * 			exists (<=> Message not in protocol).
	 */
	private ServerSideHandler generateMessage(String message){
		//TODO check if BEGIN and ENDSESSION are needed.
		if (message.equals(MessageHandler.CONTAIN_FILE)){
			return new ContainFile();
		}
		else if (message.equals(MessageHandler.DONT_CONTAIN_FILE)){
			return new DontContainFile();
		}
		else if (message.equals(MessageHandler.GOODBYE)){
			return new Goodbye();
		}
		else if (message.equals(MessageHandler.WANT_ALL_FILES)){
			return new WantAllFiles();
		}
		else if (message.equals(MessageHandler.WANT_FILE)){
			return new WantFile();
		}
		else if (message.equals(MessageHandler.WANT_SERVERS)){
			return new WantServers();
		}
		else{
			return NO_SUCH_MESSAGE;
		}
	}
	
	//TODO check if this method is needed.
	/**
	 * Will send the client Error
	 */
	private void terminateErrorSession(){
		ServerMessageSender.error(_clientOutput);
		closeStreams();
	}
	
	//TODO ask can session contain only begin & EndSession?
	public void run(){
		try {
			//Reads first message
			String inputMessage=this._clientInput.readUTF();
			
			//Checks for begin Begin
			if (!inputMessage.equals(MessageHandler.BEGIN)){
				System.out.println("Server didn't get Begin from the client");
				ServerMessageSender.error(_clientOutput);
				return; 
			}
			
			
			//GOT BEGIN - handling
			System.out.println("Server got Begin from the client");
			ServerSideHandler currentMessage = new Begin();
			if (currentMessage.runAsServer(_server, _clientIP, 
						_clientInput, _clientOutput)==FAILURE){
				System.out.println("Server Begin Failed");
				ServerMessageSender.error(_clientOutput);

				return;
			}
			
			
			System.out.println("Server initiated a new session with: "+_clientIP);
			
			//TODO  - it should accept only ONE COMMAND!
			//GOT NEW MESSAGE
			inputMessage=this._clientInput.readUTF();
			if (!inputMessage.equals(MessageHandler.END_SESSION)
								&& !inputMessage.equals(MessageHandler.GO_AWAY)){
				
				System.out.println("Server got "+inputMessage+" from: "+_clientIP);
				if ((currentMessage=generateMessage(inputMessage))==NO_SUCH_MESSAGE){
					ServerMessageSender.error(_clientOutput);
					System.out.println("Server "+inputMessage+" from: "+_clientIP+" is Illegal");
					return;
					
				}
				
				//Handles message
				if (currentMessage.runAsServer(_server, _clientIP, 
										_clientInput, _clientOutput)==FAILURE){
					ServerMessageSender.error(_clientOutput);
					System.out.println("Server "+inputMessage+" from: "+_clientIP+" Failed");
					return;
				}
				inputMessage=this._clientInput.readUTF();
			}
			
			//GOT GOAWAY
			if (inputMessage.equals(MessageHandler.GO_AWAY)){
				System.out.println("Server got GoAway from: "+_clientIP);
				currentMessage = new GoAway();
				if (currentMessage.runAsServer(_server, _clientIP, _clientInput,
														_clientOutput)==FAILURE){
					System.out.println("Server GoAway from: "+_clientIP+" Failed");
					ServerMessageSender.error(_clientOutput);
					return;
				}
				else{ //GOAWAY completed successfully
					return;
				}
			}
			
			
			//GOT ENDSESSION
			if (inputMessage.equals(MessageHandler.END_SESSION)){
			System.out.println("Server got EndSession from: "+_clientIP);
			currentMessage = new EndSession();
			if (currentMessage.runAsServer(_server, _clientIP, 
											_clientInput, _clientOutput)==FAILURE){
				System.out.println("Server EndSession from: "+_clientIP+" Failed");
				ServerMessageSender.error(_clientOutput);
				return;
				}
			}
			else{
				System.out.println("Server EndSession from: "+_clientIP+" was not sent");
				ServerMessageSender.error(_clientOutput);
				return;
			}
			System.out.println();
			_server.printDB();
			System.out.println();
		} catch (SocketTimeoutException e) {
			System.out.println("Client "+_clientIP+" Timed Out");
			ServerMessageSender.error(_clientOutput);
			return;
		} catch (IOException e) {
			System.out.println("Client "+_clientIP+" Streams failed");
			ServerMessageSender.error(_clientOutput);
			return;
		}finally{
			System.out.println("Closing streams...");
			closeStreams();
		}
	}
}
